(ns mage.kondo
  (:require
   [clojure.string :as str]
   [mage.shell :as shell]
   [mage.util :as u]))

(set! *warn-on-reflection* true)

(defn- copy-configs!
  "Copy over Kondo configs from libraries we use."
  []
  (let [[classpath] (shell/sh {:quiet? true} "clojure" "-A:dev" "-Spath")]
    (println "Copying Kondo configs from dependencies...")
    (shell/sh "clojure" "-M:kondo"
              "--copy-configs"
              "--dependencies"
              "--lint" classpath
              "--skip-lint"
              "--parallel")))

(defn- current-deps-edn-hash []
  (first (shell/sh {:quiet? true} "md5sum" "deps.edn")))

(def ^:private saved-deps-edn-hash-filename
  "File to store the MD5 checksum for `deps.edn`."
  (str u/project-root-directory "/.clj-kondo/.deps.edn.md5sum"))

(defn- saved-deps-edn-hash
  []
  (try
    (str/trim (slurp saved-deps-edn-hash-filename))
    (catch Exception _ nil)))

(defn- copy-configs-if-needed!
  "Copy Kondo configs for dependencies only if `deps.edn` has changed since last time we did it."
  []
  (let [current-hash (current-deps-edn-hash)]
    (when-not (= current-hash (saved-deps-edn-hash))
      (copy-configs!)
      (spit saved-deps-edn-hash-filename current-hash))))

(defn- clear-cache!
  "Clear cache and delete the broken type signatures automatically generated by Malli.

  TODO -- it's only really important to clear the cache when we switch branches; maybe we can keep track of what
  branch we last ran on and only clear the cache when it changes."
  []
  (shell/sh "rm" "-rf" ".clj-kondo/metosin/malli-types-clj/")
  (shell/sh "rm" "-rf" ".clj-kondo/.cache"))

(defn- kondo*
  [args]
  (copy-configs-if-needed!)
  (clear-cache!)
  (let [command           (if (empty? args)
                            (do
                              (println "Hunker down, we're running kondo against everything we usually lint...")
                              (println "(Maybe you meant to run it against a certain file?)")
                              ["-M:kondo:kondo/all"])
                            (list* "-M:kondo" "--lint" args))
        _ (u/debug "command: " command)]
    (println "Running Kondo on:" args)
    (let [{:keys [exit], :or {exit -1}} (apply shell/sh* "clojure" command)]
      (System/exit exit))))

(defn kondo
  "Run Kondo against our project. With no args, runs Kondo against everything we normally lint. Otherwise args are
  passed directly to Kondo e.g.

    ./bin/mage.sh kondo # run Kondo against everything"
  [cli-args]
  (kondo* cli-args))

(defn- kondo-updated* [diff-target]
  (let [diff-target   (or diff-target "HEAD")
        updated-files (u/updated-clojure-files diff-target)]
    (when (empty? updated-files)
      (println "No updated Clojure source files.")
      (System/exit 0))
    (printf "Linting Clojure source files that have changes compared to %s...\n" diff-target)
    (println "Files:")
    (doseq [filename updated-files]
      (println "  " filename))
    (let [{:keys [exit], :or {exit -1}} (apply shell/sh* "clojure" "-M:kondo" "--lint" updated-files)]
      (System/exit exit))))

(defn kondo-updated
  "Run Kondo against files that have been changed relative to a Git ref (default `HEAD`).

    # run Kondo on files with changes relative to HEAD
    ./bin/mage.sh kondo-updated

    # run Kondo on files with changes relative to master
    ./bin/mage.sh kondo-updated master"
  [cli-args]
  (kondo-updated* (first cli-args)))
